////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016-2018 Qualcomm Technologies, Inc.
// All Rights Reserved.
// Confidential and Proprietary - Qualcomm Technologies, Inc.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @file  camximagebuffermanager.cpp
///
/// @brief Image buffer manager implementation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "camximagebuffermanager.h"
#include "camximageformatutils.h"
#include "camxmem.h"

CAMX_NAMESPACE_BEGIN

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::Create
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult ImageBufferManager::Create(
    const CHAR*          pBufferManagerName,
    ImageBufferManager** ppImageBufferManager)
{
    CamxResult          result              = CamxResultENoMemory;
    ImageBufferManager* pImageBufferManager = CAMX_NEW ImageBufferManager();
    if (NULL != pImageBufferManager)
    {
        result = pImageBufferManager->Initialize(pBufferManagerName);
        if (CamxResultSuccess == result)
        {
            *ppImageBufferManager = pImageBufferManager;
        }
        else
        {
            CAMX_DELETE pImageBufferManager;
            pImageBufferManager = NULL;
        }
    }
    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::Destroy
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID ImageBufferManager::Destroy()
{
    CAMX_DELETE this;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::ImageBufferManager
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ImageBufferManager::ImageBufferManager()
{

}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::~ImageBufferManager
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ImageBufferManager::~ImageBufferManager()
{
    // Clean up buffers and allocated resources no matter what.
    FreeBuffers(FALSE);
    FreeResources();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::Initialize
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult ImageBufferManager::Initialize(
    const CHAR* pBufferManagerName)
{
    CamxResult result = CamxResultSuccess;

    CAMX_ASSERT(NULL != pBufferManagerName);
    OsUtils::StrLCpy(m_pBufferManagerName, pBufferManagerName, MaxStringLength64);

    m_pLock = Mutex::Create("ImageBufferManager");
    CAMX_ASSERT(NULL != m_pLock);

    m_pWaitFreeBuffer = Condition::Create("ImageBuffer available");
    CAMX_ASSERT(NULL != m_pWaitFreeBuffer);

    if ((NULL == m_pLock) || (NULL == m_pWaitFreeBuffer))
    {
        result = CamxResultEFailed;
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::InitializeBuffers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult ImageBufferManager::InitializeBuffers(
    const ImageFormat* pFormat,
    UINT               maxBufferCount,
    UINT               immediateBufferCount,
    UINT32             flags,
    const INT32*       pDeviceIndices,
    UINT               deviceCount,
    BOOL               allocateBufferMemory,
    UINT               numBatchedFrames)
{
    CamxResult result = CamxResultSuccess;

    CAMX_ASSERT(NULL != pFormat);
    CAMX_ASSERT(immediateBufferCount <= maxBufferCount);

    if (NULL == pFormat)
    {
        result = CamxResultEInvalidArg;
    }

    if (CamxResultSuccess == result)
    {
        m_maxBufferCount = maxBufferCount;

        // Store the inputs provided to the buffer manager as this information is common to all
        // the buffers allocated
        m_threadData.deviceCount          = deviceCount;
        m_threadData.flags                = flags;
        m_threadData.imageFormat          = *pFormat;
        m_threadData.numBatchedFrames     = numBatchedFrames;
        m_threadData.allocateBufferMemory = allocateBufferMemory;
        m_threadData.pDeviceIndices       = NULL;

        if (0 < deviceCount)
        {
            m_threadData.pDeviceIndices = CAMX_NEW INT32[deviceCount];

            // Copy the array of device indices that access this buffer
            if (NULL != m_threadData.pDeviceIndices)
            {
                Utils::Memcpy(m_threadData.pDeviceIndices, pDeviceIndices,
                    (sizeof(pDeviceIndices[0]) * deviceCount));
            }
        }

        m_immediateAllocBufferCount = immediateBufferCount;

        // If the total number of buffers to allocate is less than the minimum number of buffers to be allocated,
        // the number of buffers to be immediately allocated is updated accordingly
        if (maxBufferCount < m_immediateAllocBufferCount)
        {
            m_immediateAllocBufferCount = maxBufferCount;
        }

        // Allocate the minimum number of buffers needed immediately serially
        for (UINT i = 0; i < m_immediateAllocBufferCount; i++)
        {
            ImageBuffer* pBuffer = CAMX_NEW ImageBuffer(this);
            CAMX_ASSERT(NULL != pBuffer);
            if (NULL == pBuffer)
            {
                result = CamxResultENoMemory;
            }

            if (CamxResultSuccess == result)
            {
                if (TRUE == allocateBufferMemory)
                {
                    CAMX_LOG_VERBOSE(CamxLogGroupUtils, "[buf.camx] [%d/%d] allocating buffer, size:%d ",
                        i,
                        m_immediateAllocBufferCount,
                        ImageFormatUtils::GetTotalSize(pFormat));
                    // Allocate the buffer
                    result = pBuffer->Allocate(m_pBufferManagerName,
                                               pFormat,
                                               flags,
                                               pDeviceIndices,
                                               deviceCount,
                                               numBatchedFrames);

                    CAMX_ASSERT(CamxResultSuccess == result);
                }

                if (CamxResultSuccess == result)
                {
                    LDLLNode* pNode = static_cast<LDLLNode*>(CAMX_CALLOC(sizeof(LDLLNode)));

                    if (NULL != pNode)
                    {
                        pNode->pData = pBuffer;

                        // Add the buffer to the free buffer list
                        m_freeBufferList.InsertToTail(pNode);

                        CAMX_LOG_VERBOSE(CamxLogGroupUtils, "[%s] ImageBuffer %x", m_pBufferManagerName, pBuffer);
                    }
                    else
                    {
                        result = CamxResultENoMemory;
                    }
                }
            }

            if (CamxResultSuccess != result)
            {
                // There was an error, break out and clean up.
                break;
            }
        }

        if (CamxResultSuccess != result)
        {
            FreeBuffers(FALSE);
        }
    }

    CAMX_LOG_VERBOSE(CamxLogGroupUtils, "[%s] Free buffers = %d Busy buffers = %d",
                     m_pBufferManagerName,
                     m_freeBufferList.NumNodes(),
                     m_busyBufferList.NumNodes());

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::GetImageBuffer
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ImageBuffer* ImageBufferManager::GetImageBuffer()
{
    m_pLock->Lock();

    LDLLNode*    pNode   = m_freeBufferList.RemoveFromHead();
    ImageBuffer* pBuffer = NULL;

    // Check the free List for an available buffer
    if (NULL != pNode)
    {
        pBuffer = static_cast<ImageBuffer*>(pNode->pData);

        CAMX_FREE(pNode);
        pNode = NULL;

        CAMX_LOG_VERBOSE(CamxLogGroupUtils, "[%s] Returning a buffer from the free list", m_pBufferManagerName);
    }

    // If no buffer in the free list, search the entire busy list for buffers with reference count 0. If more than one
    // buffer is found with a reference count of 0, the additional buffers will be returned to the free list.
    if (NULL == pBuffer)
    {
        pNode = m_busyBufferList.Head();

        while (NULL != pNode)
        {
            ImageBuffer* pBusyBuffer = static_cast<ImageBuffer*>(pNode->pData);
            LDLLNode*    pNext       = LightweightDoublyLinkedList::NextNode(pNode);

            if (0 == pBusyBuffer->GetReferenceCount())
            {
                m_busyBufferList.RemoveNode(pNode);

                if (NULL == pBuffer)
                {
                    // We will keep this buffer
                    pBuffer = pBusyBuffer;
                    CAMX_FREE(pNode);
                    pNode = NULL;
                }
                else
                {
                    // We already found a buffer, but we can move this unreferenced busy buffer to the free list.
                    m_freeBufferList.InsertToTail(pNode);
                }
            }
            pNode = pNext;
        }

        if (NULL != pBuffer)
        {
            CAMX_LOG_VERBOSE(CamxLogGroupUtils, "[%s] Returning a buffer from the busy list", m_pBufferManagerName);
        }
    }

    if (NULL == pBuffer)
    {
        UINT numOfFreeBuffers = m_freeBufferList.NumNodes();
        UINT numOfBusyBuffers = m_busyBufferList.NumNodes();

        // If no free buffers were found either in the free list or the busy list, we check to see if an additional buffer
        // can be allocated immediately
        if ((numOfFreeBuffers + numOfBusyBuffers) < m_maxBufferCount)
        {
            CamxResult result = CamxResultSuccess;

            pBuffer = CAMX_NEW ImageBuffer(this);
            CAMX_ASSERT(NULL != pBuffer);
            if (NULL == pBuffer)
            {
                CAMX_LOG_ERROR(CamxLogGroupUtils, "[%s] Ran out of memory to allocate", m_pBufferManagerName);

                result = CamxResultENoMemory;
            }

            if (CamxResultSuccess == result)
            {
                if (TRUE == m_threadData.allocateBufferMemory)
                {
                    // Allocate the buffer
                    result = pBuffer->Allocate(m_pBufferManagerName,
                                               &m_threadData.imageFormat,
                                               m_threadData.flags,
                                               m_threadData.pDeviceIndices,
                                               m_threadData.deviceCount,
                                               m_threadData.numBatchedFrames);

                    CAMX_ASSERT(CamxResultSuccess == result);

                    if (CamxResultSuccess != result)
                    {
                        CAMX_LOG_ERROR(CamxLogGroupUtils, "[%s] ImageBuffer allocation failed with result = %d",
                                       m_pBufferManagerName, result);
                    }
                }

                CAMX_LOG_VERBOSE(CamxLogGroupUtils, "[%s] ImageBuffer %p", m_pBufferManagerName, pBuffer);
            }
        }
        else
        {
            CAMX_LOG_ERROR(CamxLogGroupUtils, "[%s] Unable to allocate free buffers and no more free buffers available",
                           m_pBufferManagerName);
        }
    }

    if (NULL == pBuffer)
    {
        CAMX_LOG_VERBOSE(CamxLogGroupUtils, "[%s] About to wait for a free buffer", m_pBufferManagerName);

        // If no free buffers were found either in the free list or the busy list and no more buffers can be allocated,
        // we wait until a busy buffer becomes free
        m_pWaitFreeBuffer->Wait(m_pLock->GetNativeHandle());

        pNode = m_freeBufferList.RemoveFromHead();

        // Check the free List for an available buffer
        if (NULL != pNode)
        {
            pBuffer = static_cast<ImageBuffer*>(pNode->pData);
            CAMX_FREE(pNode);
            pNode   = NULL;

            CAMX_LOG_VERBOSE(CamxLogGroupUtils, "[%s] Returning a buffer from the free list after wait ended",
                             m_pBufferManagerName);
        }
        else
        {
            CAMX_LOG_ERROR(CamxLogGroupUtils, "[%s] Unable to find a buffer in the free buffer list after wait ended",
                           m_pBufferManagerName);
        }
    }

    // Found a buffer. Increment it's reference count and add to the busy list.
    if (NULL != pBuffer)
    {
        CAMX_LOG_VERBOSE(CamxLogGroupUtils, "[%s] GetImageBuffer is returning ImageBuffer %p", m_pBufferManagerName, pBuffer);
        m_pLock->Unlock();
        pBuffer->AddImageReference();
        m_pLock->Lock();

        pNode = static_cast<LDLLNode*>(CAMX_CALLOC(sizeof(LDLLNode)));

        if (NULL != pNode)
        {
            pNode->pData = pBuffer;
            m_busyBufferList.InsertToTail(pNode);
        }
    }

    CAMX_LOG_VERBOSE(CamxLogGroupUtils, "[%s] Free buffers = %d Busy buffers = %d",
                     m_pBufferManagerName,
                     m_freeBufferList.NumNodes(),
                     m_busyBufferList.NumNodes());

    m_pLock->Unlock();

    return pBuffer;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::AddReference
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
UINT ImageBufferManager::AddReference(
    ImageBuffer* pImageBuffer)
{
    BOOL      doneFlag = FALSE;
    UINT      count    = 0;
    LDLLNode* pNode    = m_freeBufferList.Head();

    m_pLock->Lock();

    while (NULL != pNode)
    {
        // Found the image buffer in free list
        if (pImageBuffer == static_cast<ImageBuffer*>(pNode->pData))
        {
            // Add reference to the image buffer
            count = pImageBuffer->AddImageReference();

            // Move if from free list to busy list
            m_freeBufferList.RemoveNode(pNode);
            m_busyBufferList.InsertToTail(pNode);

            CAMX_LOG_VERBOSE(CamxLogGroupCore, "[%s] Image buffer %p in free list, reference count after adding = %d",
                             GetBufferManagerName(), pImageBuffer, pImageBuffer->GetReferenceCount());

            doneFlag = TRUE;
            break;
        }
        pNode = LightweightDoublyLinkedList::NextNode(pNode);
    }

    if (FALSE == doneFlag)
    {
        pNode = m_busyBufferList.Head();

        // Didn't find the image buffer in free list, then search busy list
        while (NULL != pNode)
        {
            // Found the image buffer in busy list
            if (pImageBuffer == static_cast<ImageBuffer*>(pNode->pData))
            {
                // Add reference to the image buffer
                count = pImageBuffer->AddImageReference();

                CAMX_LOG_VERBOSE(CamxLogGroupCore, "[%s] Image buffer %p in busy list, reference count after adding = %d",
                                 GetBufferManagerName(), pImageBuffer, pImageBuffer->GetReferenceCount());

                doneFlag = TRUE;
                break;
            }
            pNode = LightweightDoublyLinkedList::NextNode(pNode);
        }
    }

    if (FALSE == doneFlag)
    {
        CAMX_LOG_ERROR(CamxLogGroupCore, "[%s] Add reference to image buffer %p failed.", GetBufferManagerName(), pImageBuffer);
    }

    m_pLock->Unlock();

    return count;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::ReleaseReference
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
UINT ImageBufferManager::ReleaseReference(
    ImageBuffer* pImageBuffer)
{
    BOOL doneFlag = FALSE;
    UINT count    = 0;
    m_pLock->Lock();

    if (0 < pImageBuffer->GetReferenceCount())
    {
        LDLLNode* pNode = m_busyBufferList.Head();

        while (NULL != pNode)
        {
            // Found the image buffer in busy list
            if (pImageBuffer == static_cast<ImageBuffer*>(pNode->pData))
            {
                // Release reference to the image buffer
                count = pImageBuffer->ReleaseImageReference();

                CAMX_LOG_VERBOSE(CamxLogGroupCore, "[%s] Image buffer %p in busy list, reference count after releasing = %d",
                                 GetBufferManagerName(), pImageBuffer, count);

                if (0 == count)
                {
                    // Move the buffer to the free list
                    m_busyBufferList.RemoveNode(pNode);
                    m_freeBufferList.InsertToTail(pNode);

                    // Signal any threads waiting for a free buffer
                    m_pWaitFreeBuffer->Signal();
                }
                doneFlag = TRUE;
                break;
            }
            pNode = LightweightDoublyLinkedList::NextNode(pNode);
        }

        if (FALSE == doneFlag)
        {
            CAMX_LOG_ERROR(CamxLogGroupCore, "[%s] Didn't find the image buffer %p in busy list.",
                           GetBufferManagerName(), pImageBuffer);
        }
    }
    else
    {
        CAMX_LOG_ERROR(CamxLogGroupCore, "[%s] Release reference to image buffer %p failed. Reference count is already 0",
                       GetBufferManagerName(), pImageBuffer);
    }

    m_pLock->Unlock();

    return count;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::ReleaseBuffers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult ImageBufferManager::ReleaseBuffers(
    BOOL isPartialRelease)
{
    m_pLock->Lock();

    CamxResult result = CamxResultSuccess;

    FreeBuffers(isPartialRelease);

    m_pLock->Unlock();

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::GetBufferManagerName
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CHAR* ImageBufferManager::GetBufferManagerName()
{
    return m_pBufferManagerName;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::FreeBuffers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID ImageBufferManager::FreeBuffers(
    BOOL isPartialRelease)
{
    while (TRUE)
    {
        if (TRUE == isPartialRelease)
        {
            if ((m_freeBufferList.NumNodes() + m_busyBufferList.NumNodes()) <= m_immediateAllocBufferCount)
            {
                break;
            }
        }

        LDLLNode* pNode = m_freeBufferList.RemoveFromHead();

        if (NULL != pNode)
        {
            ImageBuffer* pBuffer = static_cast<ImageBuffer*>(pNode->pData);

            CAMX_ASSERT((NULL != pBuffer) && (0 == pBuffer->GetReferenceCount()));

            CAMX_LOG_VERBOSE(CamxLogGroupUtils,
                            "[%s] Releasing ImageBuffer %x from FreeList  with reference count of %d",
                            m_pBufferManagerName,
                            pBuffer,
                            pBuffer->GetReferenceCount());

            pBuffer->Release();
            CAMX_DELETE pBuffer;
            pBuffer = NULL;
            CAMX_FREE(pNode);
            pNode   = NULL;
        }
        else
        {
            break;
        }
    }

    while (TRUE)
    {
        if (TRUE == isPartialRelease)
        {
            if ( (m_freeBufferList.NumNodes() + m_busyBufferList.NumNodes()) <= m_immediateAllocBufferCount)
            {
                break;
            }
        }

        LDLLNode* pNode = m_busyBufferList.RemoveFromHead();

        if (NULL != pNode)
        {
            ImageBuffer* pBuffer = static_cast<ImageBuffer*>(pNode->pData);

            CAMX_ASSERT((NULL != pBuffer) && (0 == pBuffer->GetReferenceCount()));

            CAMX_LOG_VERBOSE(CamxLogGroupUtils,
                                "[%s] Releasing ImageBuffer %x from BusyList with reference count of %d",
                                m_pBufferManagerName,
                                pBuffer,
                                pBuffer->GetReferenceCount());

            pBuffer->Release();
            CAMX_DELETE pBuffer;
            pBuffer = NULL;
            CAMX_FREE(pNode);
            pNode = NULL;
        }
        else
        {
            break;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ImageBufferManager::FreeResources
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID ImageBufferManager::FreeResources()
{
    if (NULL != m_threadData.pDeviceIndices)
    {
        CAMX_DELETE [] m_threadData.pDeviceIndices;
        m_threadData.pDeviceIndices = NULL;
    }

    if (NULL != m_pLock)
    {
        m_pLock->Destroy();
        m_pLock = NULL;
    }

    if (NULL != m_pWaitFreeBuffer)
    {
        m_pWaitFreeBuffer->Destroy();
        m_pWaitFreeBuffer = NULL;
    }
}

CAMX_NAMESPACE_END
